package com.rengwuxian.coursecoroutines._4_flow

import com.rengwuxian.coursecoroutines.common.unstableGitHub
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.util.concurrent.TimeoutException
import kotlin.coroutines.EmptyCoroutineContext

/**
 * catch()操作符
 * Flow的异常可见性、try/catch、catch()
 *
 * catch{}操作符能捕获上游Flow的异常，比如上游读数据库的过程中抛出了异常，这个catch里面就可以拿到。
 * 但是下游的异常是捕获不到的，比如collect{}大括号里抛出的异常，不会触发catch的代码块，包括catch下方
 * 如果有操作符，这些下方的操作符里异常，catch()也是拿不到的。
 *
 * catch{}的效果相当于上游整体逻辑用try/catch包住代码块，然后把emit的异常给过滤掉（emit的异常不会被catch{}吞掉）正常往下流动这个异常，
 * 直到collect{}大括号中，最后被collect()所在的协程拦截。
 * 即：catch{}操作符只会捕获上游异常，不会捕获下游异常的操作符。如果下游抛异常，catch{}大括号里面的内容是不会走的。
 *
 * catch()不会捕获CancellationException（用于取消协程），即使上游抛出了这个异常，也不会捕获。
 * catch()只会捕获上游的异常，不会捕获自己的异常，如果连续写多个catch{}操作符，下面的catch{}会抓住和上面catch之间代码抛出的异常。
 *
 * catch()的作用：
 * Flow上游是一个管道，每过一个操作符，就相当于续接一个管道，每个管道可能增加内容，可能删除内容，会做一些自己的事。
 * 如果最上游的管道在生产数据的时候出了问题，但是用try/catch解决了，仍然可以正常生产数据，如果不解决，则异常往下抛，
 * 会让每个Flow都结束掉，会毁掉整个管道。
 * 如果下游有catch{}操作符来按住这个异常，就可以进行接管，可以生产数据。相当于在上面坏掉的管道连接处，插了一根侧管，继续发送数据，下游是感知不到的。
 *
 * try/catch是在Flow里面进行工作的，而catch操作符是在Flow后面工作的，
 * 内部还有机会处理生产过程中发送的异常，从而让生产流程可以继续，Flow可以继续活着，
 * 而当异常向下到达catch这里，Flow就已经死掉了。catch()只是无奈之下的接管，往往做不了什么修复工作，只能做些收尾工作。
 * 比如下面例子的输出：
 * Data: 1
 * Data: 2
 * catch(): java.lang.RuntimeException: flow() error
 * Data: 100
 * Data: 200
 * Data: 300
 *
 * 能用try/catch就用，用不了就用catch().
 * 比如：无法修改Flow上游的代码，使用的时候就是拿到一个现成的Flow。
 */
fun main() = runBlocking<Unit> {
  val scope = CoroutineScope(EmptyCoroutineContext)
  val flow1 = flow {
    for (i in 1..5) {
      // 数据库读数据
      // 网络请求
      if (i == 3) {
        throw RuntimeException("flow() error")
      } else {
        emit(i)
      }
    }
  }.catch {
    println("catch(): $it")
    emit(100)
    emit(200)
    emit(300)
//    throw RuntimeException("Exception from catch()")
  }/*.onEach { throw RuntimeException("Exception from onEach()") }
    .catch { println("catch() 2: $it") }*/
  scope.launch {
    try {
      flow1.collect {
        /*val contributors = unstableGitHub.contributors("square", "retrofit")
        println("Contributors: $contributors")*/
        println("Data: $it")
      }
    } catch (e: TimeoutException) {
      println("Network error: $e")
    }
  }
  delay(10000)
}